﻿// -----------------------------------------------------------------------
// <copyright file="FormDesigner.cs" company="">
// TODO: Update copyright text.
// </copyright>
// -----------------------------------------------------------------------

namespace FormCreator.ViewModel
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel.Composition;
    using System.Linq;
    using System.Text;
    using System.Windows.Input;
    using FormCreator.Model;
    using GongSolutions.Wpf.DragDrop;
    using Microsoft.Practices.Prism.Commands;
    using Microsoft.Practices.Prism.ViewModel;
    using WPForms.Common.Model;

    /// <summary>
    /// View model for the form designer.
    /// </summary>
    [Export]
    [PartCreationPolicy(CreationPolicy.Shared)]
    public class FormDesignerViewModel : NotificationObject, IDropTarget
    {
        /// <summary>
        /// The currently selected element.
        /// </summary>
        private FormElementViewModel selectedElement;

        /// <summary>
        /// The property editor for the currently selected item. 
        /// </summary>
        private ElementPropertiesViewModel elementProperties;

        /// <summary>
        /// The property editor for the current form.
        /// </summary>
        private ElementPropertiesViewModel formProperties;

        /// <summary>
        /// Initializes a new instance of the <see cref="FormDesignerViewModel" /> class.
        /// </summary>
        public FormDesignerViewModel()
        {
            this.Reset();
            this.LoadCommand = new DelegateCommand(this.Load);
            this.SaveCommand = new DelegateCommand(this.Save);
            this.DeleteElementCommand = new DelegateCommand(this.DeleteSelected, () => this.SelectedElement != null && this.SelectedElement.Parent != null);
        }

        /// <summary>
        /// Gets the collection of the created sections.
        /// </summary>
        public IEnumerable<FormElementViewModel> Sections { get; private set; }

        /// <summary>
        /// Gets the currently designed form instance.
        /// </summary>
        public FormInstance Form { get; private set; }

        /// <summary>
        /// Gets the command responsible for loading a form from a xaml file.
        /// </summary>
        public ICommand LoadCommand { get; private set; }

        /// <summary>
        /// Gets the command responsible for saving the edited form.
        /// </summary>
        public ICommand SaveCommand { get; private set; }

        /// <summary>
        /// Gets the command responsible for deleting the currently selected element.
        /// </summary>
        public DelegateCommand DeleteElementCommand { get; private set; }

        /// <summary>
        /// Gets or sets the currently selected form element.
        /// </summary>
        public FormElementViewModel SelectedElement
        {
            get
            {
                return this.selectedElement;
            }

            set
            {
                this.selectedElement = value;
                this.DeleteElementCommand.RaiseCanExecuteChanged();
                if (this.SelectedElement != null)
                {
                    this.Properties = new ElementPropertiesViewModel(this.SelectedElement.Element);
                }
            }
        }

        /// <summary>
        /// Gets the property editor view model for the currently selected element.
        /// </summary>
        public ElementPropertiesViewModel Properties
        {
            get
            {
                return this.elementProperties;
            }

            private set
            {
                this.elementProperties = value;
                this.RaisePropertyChanged(() => this.Properties);
            }
        }

        /// <summary>
        /// Gets the property editor view model for the currently edited form.
        /// </summary>
        public ElementPropertiesViewModel FormProperties
        {
            get
            {
                return this.formProperties;
            }

            private set
            {
                this.formProperties = value;
                this.RaisePropertyChanged(() => this.FormProperties);
            }
        }

        /// <summary>
        /// Resets the designer to use an empty form.
        /// </summary>
        /// <param name="form">The form, whcih will be loaded. If null is supplied a new form instance is created.</param>
        public void Reset(FormInstance form = null)
        {
            if (form == null)
            {
                this.Form = new FormInstance();
                this.Form.MainSection = new FormSection() { IsEnabled = true };
            }
            else
            {
                this.Form = form;
            }

            this.FormProperties = new ElementPropertiesViewModel(this.Form);
            this.Sections = new List<FormElementViewModel>() { CreateViewModel(this.Form.MainSection) };
            this.RaisePropertyChanged(() => this.Form);
            this.RaisePropertyChanged(() => this.Sections);
        }

        /// <summary>
        /// Updates the current drag state.
        /// </summary>
        /// <param name="dropInfo">Information about the drag.</param>
        /// <remarks>
        /// To allow a drop at the current drag position, the <see cref="P:GongSolutions.Wpf.DragDrop.DropInfo.Effects" /> property on
        /// <paramref name="dropInfo" /> should be set to a value other than <see cref="F:System.Windows.DragDropEffects.None" />
        /// and <see cref="P:GongSolutions.Wpf.DragDrop.DropInfo.Data" /> should be set to a non-null value.
        /// </remarks>
        public void DragOver(DropInfo dropInfo)
        {
            var targetElement = dropInfo.TargetItem as FormElementViewModel;
            if (targetElement == null)
            {
                return;
            }

            if (dropInfo.Data is ElementType)
            {
                dropInfo.Effects = System.Windows.DragDropEffects.Copy;
                dropInfo.DropTargetAdorner = DropTargetAdorners.Insert;
                if (targetElement.IsSection)
                {
                    targetElement.IsExpanded = true;
                }
            }
            else if (dropInfo.Data is FormElementViewModel)
            {
                dropInfo.Effects = System.Windows.DragDropEffects.Move;
                dropInfo.DropTargetAdorner = DropTargetAdorners.Insert;
                if (targetElement.IsSection)
                {
                    targetElement.IsExpanded = true;
                }
            }
        }

        /// <summary>
        /// Performs a drop.
        /// </summary>
        /// <param name="dropInfo">Information about the drop.</param>
        public void Drop(DropInfo dropInfo)
        {
            var elementTypeData = dropInfo.Data as ElementType;
            var target = (FormElementViewModel)dropInfo.TargetItem;
            FormElement newElement = null;
            if (elementTypeData != null)
            {
                newElement = (FormElement)Activator.CreateInstance(elementTypeData.Type);
                newElement.IsEnabled = true;
            }

            var viewModelData = dropInfo.Data as FormElementViewModel;
            int removedIndex = int.MaxValue;
            if (viewModelData != null)
            {
                removedIndex = viewModelData.Parent.Children.IndexOf(viewModelData);
                viewModelData.Parent.Children.Remove(viewModelData);
                ((FormSection)viewModelData.Parent.Element).Children.Remove(viewModelData.Element);
                newElement = viewModelData.Element;
            }

            var newViewModel = CreateViewModel(newElement);
            if (target.IsSection)
            {
                target.AddChild(newViewModel);
                var section = (FormSection)target.Element;
                section.Children.Insert(0, newElement);
            }
            else
            {
                target.Parent.AddChild(CreateViewModel(newElement), target);
                var section = (FormSection)target.Parent.Element;
                var index = section.Children.IndexOf(target.Element);
                if (removedIndex < index)
                {
                    // has to increase index if the removed item was before the inserted item.
                    index += 2;
                }

                section.Children.Insert(index, newElement);
            }
        }

        /// <summary>
        /// Finds the parent of the given element.
        /// </summary>
        /// <param name="section">The section, which serves as a search start point.</param>
        /// <param name="element">The element.</param>
        /// <returns>The found parent. If not found null is returned.</returns>
        private static FormSection FindParent(FormSection section, FormElement element)
        {
            if (section.Children.Contains(element))
            {
                return section;
            }

            foreach (var child in section.Children)
            {
                var childSection = child as FormSection;
                if (childSection != null)
                {
                    var found = FindParent(childSection, element);
                    if (found != null)
                    {
                        return found;
                    }
                }
            }

            return null;
        }

        /// <summary>
        /// Creates a <see cref="FormViewModel" /> for the given element.
        /// The element creation is recursive (view models will be created for children as well).
        /// </summary>
        /// <param name="element">The source element.</param>
        /// <param name="parent">The parent of the child to create.</param>
        /// <returns>
        /// The created element.
        /// </returns>
        private static FormElementViewModel CreateViewModel(FormElement element, FormElementViewModel parent = null)
        {
            var viewModel = new FormElementViewModel(element, parent);
            var section = element as FormSection;
            if (section != null)
            {
                foreach (var child in section.Children)
                {
                    var childViewModel = CreateViewModel(child, parent: viewModel);
                    viewModel.Children.Add(childViewModel);
                }
            }

            return viewModel;
        }

        /// <summary>
        /// Deletes the currently selected element.
        /// </summary>
        private void DeleteSelected()
        {
            ((FormSection)this.SelectedElement.Parent.Element).Children.Remove(this.SelectedElement.Element);
            this.SelectedElement.Parent.Children.Remove(this.SelectedElement);
        }

        /// <summary>
        /// Pops up an open file dialog and loads a xaml form description.
        /// </summary>
        private void Load()
        {
            var dialog = new Microsoft.Win32.OpenFileDialog()
            {
                Filter = "XAML files|*.xaml",
            };
            if (dialog.ShowDialog() == true)
            {
                FormInstance form = null;
                using (var stream = System.IO.File.OpenRead(dialog.FileName))
                {
                    form = Model.FormXamlFactory.CreateFromXaml(stream);
                }

                this.Reset(form);
            }
        }

        /// <summary>
        /// Saves the currently edited form into a new xaml file.
        /// </summary>
        private void Save()
        {
            var dialog = new Microsoft.Win32.SaveFileDialog()
            {
                Filter = "XAML files|*.xaml",
            };
            if (dialog.ShowDialog() == true)
            {
                Model.FormXamlFactory.WriteToXaml(dialog.FileName, this.Form);
            }
        }
    }
}
